<!-- 
    测试可用中文图层
    https://map.geoq.cn/arcgis/rest/services/ChinaOnlineCommunity/MapServer
    "https://wl.arcgisonline.cn/server/rest/services/wl/hightf/FeatureServer/0",
-->
<html>

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no" />
  <title>ArcGIS API for JavaScript 4.20热力图</title>
  <link rel="stylesheet" href="https://js.arcgis.com/4.20/esri/themes/light/main.css" />
  <!-- <script src="https://cdn.jsdelivr.net/npm/exif-js"></script> -->
  <script src="https://js.arcgis.com/4.20/"></script>
  <style>
    html,
    body,
    #mapView {
      padding: 0;
      margin: 0;
      height: 100%;
      width: 100%;
    }

    #btns {
      position: absolute;
      bottom: 10px;
      width: 100%;
      z-index: 1;
      text-align: center;
      cursor: pointer;
    }

    #showHeatMap,
    #removeHeatMap {
      padding: 10px;
      border: 1px solid red;
      color: red;
    }

    #showHeatMap:hover,
    #removeHeatMap:hover {
      background: red;
      color: white;
    }
  </style>
  <script>
    require(
      [
        "esri/Map",
        "esri/views/SceneView",
        "esri/layers/TileLayer",
        "esri/views/MapView",
        "esri/layers/FeatureLayer",
        "esri/core/promiseUtils",
        "esri/Graphic",
        "esri/geometry/Point",
        "esri/rest/locator",
        "dojo/_base/lang",
      ], (
        Map,
        SceneView,
        TileLayer,
        MapView,
        FeatureLayer,
        promiseUtils,
        Graphic,
        Point,
        locator,
        lang
      ) => {
      const map = new Map();
      const view = new SceneView({
        container: "mapView",
        map: map,
        zoom: 4
      });
      let dTlayer = new TileLayer({
        // url: "https://services.arcgisonline.com/arcgis/rest/services/World_Terrain_Base/MapServer"
        url: "https://map.geoq.cn/arcgis/rest/services/ChinaOnlineCommunity/MapServer",
      });
      map.add(dTlayer);
      let lineList = []
      view.on("click", e => {
        let { mapPoint: { longitude: x, latitude: y } } = e;
        lineList.push([x, y, parseInt(Math.random() * 100)]);
        console.log(JSON.stringify(lineList));
        view.hitTest(e).then(event => {
          console.log(event);
        });
      })

      view.when(() => {
        // 地图加载完成
        setTimeout(() => {
          view.goTo({
            position: {
              x: 121.42989101233015,
              y: 31.221434715924108,
              z: 100000,
              spatialReference: {
                wkid: 4326
              }
            },
            heading: 0,
            tilt: 0,
          },
            {
              duration: 3000,
              easing: 'linear'
            }
          ).catch(catchError);
        }, 500);
      })
        .then(mapInitFinish).catch(catchError);

      // 地图初始化完成
      function mapInitFinish() {
        console.log("地图初始化完成");
      }

      class HeatLayer {
        /**
       * 热力图
       * @param:{ freshenLayer() } { Function } @description 刷新layer
       * @param:{ clearCanvas() } { Function } @description 清除效果
       * @param:{ addPoint(data) } { Function } @description 添加数据  data数据格式  [[x, y, degree],[x, y, degree],[x, y, degree]]
       */
        constructor(obj) {
          let { lang, Point } = obj;
          this.view = view;
          this.lang = lang;
          this.Point = Point;
          //配置热力图的颜色、半径
          this.config = {
            radius: 20,
            gradient: {
              0.5: 'rgba(0,0, 255)',
              0.65: 'rgb(117,211,248)',
              0.7: 'rgb(0, 255, 0)',
              0.9: '#ffea00',
              1.0: 'rgb(255, 50, 0)'

              // 0:"rgba(250, 0, 0, 0)",
              // 0.5:"rgb(250, 0, 0)" ,
              // 0.7: "rgb(250, 150, 0)",
              // 0.9: "rgb(255, 255, 0)"
            }
          };
          this.canvas = null;
          this.context = null;
          this.minnum = 1;
          this.maxnum = 1;
          this.visible = true;
          // this.spatialReference = { wkid: 102100 };
          this.spatialReference = { wkid: 4326 };
          this.createCanvas();
        }
        /*创建Canvaslayer的容器canvas，添加到map的layers下面*/
        createCanvas() {
          let canvas = document.createElement("canvas");
          canvas.width = this.view.width;
          canvas.height = this.view.height;
          canvas.setAttribute("id", "heatmap");
          canvas.style.position = "absolute";
          canvas.style.top = 0;
          canvas.style.left = 0;
          let parent = document.getElementsByClassName(
            "esri-view-surface"
          )[0];
          parent.appendChild(canvas);
          this.canvas = canvas;
          this.context = document
            .getElementById("heatmap")
            .getContext("2d");
          this.startMapEventListeners();
        }
        /*转换数据*/
        convertHeatmapData(data) {
          var heatPluginData = [];
          for (let i = 0; i < data.length; i++) {
            let screenpoint = this.view.toScreen(
              new this.Point({
                x: data[i][0],
                y: data[i][1],
                spatialReference: this.spatialReference
              })
            );
            // 判断数据是否带有权重，未带有权重属性是默认为1
            if (data[0].length == 3) {
              heatPluginData.push([
                Math.round(screenpoint.x),
                Math.round(screenpoint.y),
                data[i][2]
              ]);
            } else {
              heatPluginData.push([
                Math.round(screenpoint.x),
                Math.round(screenpoint.y),
                1
              ]);
            }
            if (this.minnum > data[i][2]) {
              this.minnum = data[i][2];
            }
            if (this.maxnum < data[i][2]) {
              this.maxnum = data[i][2];
            }
          }
          return {
            points: heatPluginData,
            min: this.minnum,
            max: this.maxnum
          };
        }
        //添加点数据
        addPoint(data) {
          this.data = data;
          let points = this.convertHeatmapData(data);
          //console.log(points)
          points.points.forEach(point => {
            this.context.beginPath();
            let alpha = (point[2] - points.min) / (points.max - points.min);
            this.context.globalAlpha = alpha;
            this.context.arc(
              point[0],
              point[1],
              this.config.radius,
              0,
              Math.PI * 2,
              true
            );
            //绘制一个放射渐变样式的圆
            let gradient = this.context.createRadialGradient(
              point[0],
              point[1],
              0,
              point[0],
              point[1],
              this.config.radius
            );
            gradient.addColorStop(0, "rgba(0,0,0,1)");
            gradient.addColorStop(1, "rgba(0,0,0,0)");
            this.context.fillStyle = gradient;
            this.context.closePath();
            this.context.fill();
          });
          this.MapColors();
          //console.log('haol')
        }
        //设置渐变色带
        getColorPaint() {
          let paletteCanvas = document.createElement("canvas");
          let paletteCtx = paletteCanvas.getContext("2d");
          let gradientConfig = this.config.gradient;
          paletteCanvas.width = 256;
          paletteCanvas.height = 1;
          let gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);
          for (let key in gradientConfig) {
            gradient.addColorStop(key, gradientConfig[key]);
          }
          paletteCtx.fillStyle = gradient;
          paletteCtx.fillRect(0, 0, 256, 1);
          return paletteCtx.getImageData(0, 0, 256, 1).data;
        }
        //映射颜色
        MapColors() {
          let palette = this.getColorPaint();
          let img = this.context.getImageData(
            0,
            0,
            this.canvas.width,
            this.canvas.height
          );
          let imgData = img.data;
          for (let i = 0; i < imgData.length; i++) {
            let alpha = imgData[i];
            let offset = alpha * 4;
            if (!offset) {
              continue;
            }
            //映射颜色RGB值
            imgData[i - 3] = palette[offset];
            imgData[i - 2] = palette[offset + 1];
            imgData[i - 1] = palette[offset + 2];
          }
          this.context.putImageData(
            img,
            0,
            0,
            0,
            0,
            this.canvas.width,
            this.canvas.height
          );
        }
        /*刷新layer*/
        freshenLayer() {
          this.clearCanvas();
          this.addPoint(this.data);
        }
        /*清除渲染效果*/
        clearCanvas() {
          this.context.clearRect(0, 0, this.view.width, this.view.height);
        }
        removeCanvas() {
          this.clearCanvas()
          document.getElementById("heatmap") && document.getElementById("heatmap").remove();
        }
        /*添加监听移动和缩放*/
        startMapEventListeners() {
          let view = this.view;
          view.watch(
            "extent",
            this.lang.hitch(this, () => {
              if (!this.visible) return;
              this.freshenLayer();
            })
          );
          view.watch(
            "camera",
            this.lang.hitch(this, () => {
              if (!this.visible) return;
              this.freshenLayer();
            })
          );
        }
      }

      function catchError(error) {
        console.error(error);
      }
      var heatmap;
      document.getElementById("showHeatMap").addEventListener("click", () => {
        if (heatmap) {
          console.log("已经添加过啦 重复添加要卡死的啦");
          return
        }
        // 传入lang模块 和Point模块
        heatmap = new HeatLayer({
          lang,
          Point
        });
        let heatPoints = [[121.46638298884814, 31.250357600026337, 53], [121.47011405800387, 31.25863735310445, 11], [121.48502571952145, 31.26946406249685, 33], [121.49547213562428, 31.279656626160865, 48], [121.49696828599401, 31.283480277520642, 7], [121.50369099003768, 31.292402015073304, 0], [121.51936142941179, 31.29940785648915, 77], [121.52682650847804, 31.30322967937753, 45], [121.53354430943503, 31.30577645020285, 76], [121.54622550100314, 31.30640547804193, 3], [121.56412731085638, 31.306391039147073, 52], [121.58649543505615, 31.303180533278162, 24], [121.59243413768023, 31.29488374003492, 61], [121.60208293789444, 31.28212085315055, 23], [121.60722257098419, 31.26108160272421, 94], [121.60792658048233, 31.25024927475843, 76], [121.60934341630772, 31.231139284220603, 10], [121.60555929608745, 31.21522707470839, 40], [121.60240074818788, 31.160511606406025, 55], [121.60237447098093, 31.152244291221233, 14], [121.59788488096311, 31.14334866580241, 16], [121.59263626612154, 31.12873411753196, 26], [121.58889728960065, 31.121111894404763, 17], [121.58662726944966, 31.107133836145024, 82], [121.58362820976544, 31.097607183186952, 96], [121.57913252523913, 31.083637542229656, 4], [121.57686666059743, 31.06966796555758, 17], [121.56568559597827, 31.051903915242377, 36], [121.55081088527017, 31.035410292210404, 30], [121.53298661033448, 31.024636437346995, 87], [121.4581886644057, 31.24653843402052, 40], [121.44031817065967, 31.25354762765381, 12], [121.45223502369791, 31.254182715558024, 80], [121.43287025803922, 31.25418521229008, 7], [121.4276564167185, 31.25737039058575, 20], [121.4135033540272, 31.25864391836937, 39], [121.4045644594343, 31.258642781735904, 38], [121.3963703666188, 31.258641196372825, 47], [121.38668571883174, 31.259275716539847, 14], [121.37476652213171, 31.259271594536617, 41], [121.35465445041773, 31.257987995130314, 17], [121.3412499044715, 31.25543172459771, 8], [121.32264389755925, 31.247774284521334, 88], [121.31076225461047, 31.233753481370254, 23], [121.30486494709619, 31.211466910148964, 56], [121.3205862267189, 31.166948409733024, 86], [121.33250298353026, 31.156148145529045, 67], [121.34367351489931, 31.14407683733544, 45], [121.35335892615481, 31.127557140288474, 25], [121.36824353784435, 31.10596143871371, 48], [121.37939234649545, 31.097073342310622, 22], [121.39202352605267, 31.086280888454016, 99], [121.40464974145151, 31.07485424374463, 0], [121.41059011959157, 31.06787136545799, 70], [121.42172332907627, 31.060255216351933, 96], [121.43582077616917, 31.051369125795144, 42], [121.44398018541887, 31.045019127012857, 20], [121.4669700931223, 31.03422197132943, 55], [121.48550882163836, 31.027859760588175, 20], [121.49663222318827, 31.02531097809324, 71], [121.50915847092016, 31.022421703843403, 20], [121.46412987445379, 31.039251375017464, 76], [121.44651546132863, 31.05256704255469, 88], [121.41903412250645, 31.080960932457405, 3], [121.94289439467991, 30.91856685315386, 94], [121.94100917283876, 30.932023785257698, 8], [121.94605796645597, 30.92760714563943, 50], [121.93861017764202, 30.93160565623738, 62], [121.92859719582943, 30.935805422981776, 30], [121.91042528403287, 30.941806341239968, 83], [121.90343360146636, 30.942807193903064, 13], [121.9266378010878, 30.93155786858729, 69], [121.92819785048972, 30.933427873141415, 31], [121.93677460233285, 30.937835193543112, 3], [121.91713691821845, 30.93663806156724, 7], [121.91728633752835, 30.924215107418956, 26], [121.9300539232944, 30.922209051038646, 14], [121.92797684206116, 30.941085118580684, 52], [121.90633937219621, 30.94354113705742, 92], [121.92021508039817, 30.930933694662745, 54], [121.93571493527227, 30.920079041839465, 11], [121.93080238863138, 30.898742948009144, 6], [121.9312176240876, 30.908535412693045, 25], [121.9446722895927, 30.903283509022298, 68], [121.93405190903961, 30.88300880270456, 86], [121.91163499993532, 30.891405677606663, 84], [121.87085942209993, 30.896999565245867, 68], [121.87532126068645, 30.928136539916554, 53], [121.85939748740508, 30.933034826810758, 82], [121.86224892109534, 30.938287402009102, 58], [121.88429852968181, 30.931287621207897, 92], [121.86840905481913, 30.90119564411609, 87], [121.86720547396462, 30.87881985740462, 20], [121.89044044199854, 30.871834223520008, 79], [121.8957382513708, 30.870436847670693, 79], [121.89859187917386, 30.85716243780048, 7], [121.88718596261786, 30.85716165727914, 31], [121.86559002332544, 30.86274629107768, 8], [121.8460178003684, 30.87287197178028, 19], [121.8423139735702, 30.89629178132405, 56], [121.85081977837517, 30.93653388380577, 58], [121.86962575933704, 30.90854065333785, 77], [121.83780923018571, 30.907131906296744, 67], [121.8381807243437, 30.927077105161636, 17], [121.82660080526144, 30.995415485197363, 87], [121.78800045672656, 30.91481623190084, 99], [121.78031660441552, 30.89277596928075, 23], [121.83824595831943, 30.882682363682733, 90]]
        heatmap.addPoint(heatPoints);
        heatmap.freshenLayer();//添加热力之后刷新一次 去掉模糊点
      });
      // 删除热力图
      document.getElementById("removeHeatMap").addEventListener("click", () => {
        heatmap.removeCanvas();
        heatmap = null
      });
    });
  </script>
</head>

<body>
  <div id="btns">
    <button id="showHeatMap">显示热力图</button>
    <button id="removeHeatMap">移除热力图</button>
  </div>
  <div id="mapView"></div>
</body>

</html>